import json
import mimetypes
from datetime import datetime, timedelta, timezone as dt_timezone
from typing import Any, Dict
from urllib.parse import urljoin

from django.conf import settings
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.http import Http404, HttpRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import CreateView, DetailView, ListView, TemplateView, UpdateView

from accounts.models import UserNotification
from learning import services
from learning.forms import LLMQuestionForm, MistakeForm, NotebookEntryForm
from learning.models import (
    Mistake,
    MistakeLLMMessage,
    MistakeLLMConversation,
    NotebookAttachment,
    NotebookEntry,
    TopicTraining,
    TopicQuestion,
    TopicProgress,
)


def _build_mistake_llm_inputs(mistake: Mistake) -> Dict[str, str]:
    """构建传递给 Dify AI 的上下文信息"""
    inputs: Dict[str, str] = {
        "question": mistake.question,  # Dify 中定义的题目字段
        "title": mistake.title,
        "status": mistake.get_status_display(),
    }
    # 兼容旧版模板/测试使用的字段命名
    inputs["mistake_question"] = inputs["question"]
    inputs["mistake_title"] = inputs["title"]
    inputs["mistake_status"] = inputs["status"]
    if mistake.solution_attempt:
        inputs["student_solution"] = mistake.solution_attempt
        inputs["mistake_student_solution"] = mistake.solution_attempt
    if mistake.correct_solution:
        inputs["correct_solution"] = mistake.correct_solution
        inputs["mistake_correct_solution"] = mistake.correct_solution
    if mistake.reflection:
        inputs["reflection"] = mistake.reflection
        inputs["mistake_reflection"] = mistake.reflection
    if mistake.teacher_feedback:
        inputs["teacher_feedback"] = mistake.teacher_feedback
        inputs["mistake_teacher_feedback"] = mistake.teacher_feedback
    assigned_teacher = mistake.assigned_teacher
    if assigned_teacher:
        inputs["teacher"] = assigned_teacher.username
        inputs["mistake_teacher"] = assigned_teacher.username
    return inputs


def _resolve_dify_file_url(request: HttpRequest, raw_url: str) -> str:
    base_override = getattr(settings, "DIFY_FILES_BASE_URL", "").strip()
    force_https = getattr(settings, "DIFY_FORCE_HTTPS_FILES", False)

    if raw_url.startswith(("http://", "https://")):
        absolute_url = raw_url
    elif base_override:
        normalized_base = f"{base_override.rstrip('/')}/"
        media_url = getattr(settings, "MEDIA_URL", "")
        if media_url and raw_url.startswith(media_url):
            relative_part = raw_url[len(media_url):]
        else:
            relative_part = raw_url.lstrip("/")
        absolute_url = urljoin(normalized_base, relative_part)
    else:
        absolute_url = request.build_absolute_uri(raw_url)

    if force_https and absolute_url.startswith("http://"):
        absolute_url = "https://" + absolute_url[len("http://"):]

    return absolute_url


def _build_mistake_llm_files_remote(mistake: Mistake, request: HttpRequest) -> list[Dict[str, str]]:
    """Collect image fields and expose them to Dify via remote URLs."""
    files: list[Dict[str, str]] = []
    seen_urls: set[str] = set()
    for field_name in ("question_image", "solution_attempt_image"):
        image_field = getattr(mistake, field_name)
        if not image_field:
            continue
        try:
            file_url = image_field.url
        except (AttributeError, ValueError):
            continue
        absolute_url = _resolve_dify_file_url(request, file_url)
        if absolute_url in seen_urls:
            continue
        seen_urls.add(absolute_url)
        files.append(
            {
                "type": "image",
                "transfer_method": "remote_url",
                "url": absolute_url,
            }
        )
    return files


def _build_mistake_llm_files_upload(
    mistake: Mistake,
    *,
    client: services.DifyClient,
    user_id: str,
) -> list[Dict[str, str]]:
    files: list[Dict[str, str]] = []
    for field_name in ("question_image", "solution_attempt_image"):
        image_field = getattr(mistake, field_name)
        if not image_field:
            continue
        try:
            image_field.open("rb")
        except FileNotFoundError:
            continue
        try:
            data = image_field.read()
        finally:
            image_field.close()
        if not data:
            continue
        file_name = (image_field.name or f"{field_name}.png").split("/")[-1]
        content_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
        upload_response = client.upload_file(
            user_id=user_id,
            file_name=file_name,
            file_data=data,
            content_type=content_type,
        )
        upload_id = upload_response.get("id") or upload_response.get("file_id")
        if not upload_id:
            raise services.DifyClientError("Dify upload response missing file identifier.")
        files.append(
            {
                "type": "image",
                "transfer_method": "local_file",
                "upload_file_id": upload_id,
            }
        )
    return files


def _build_mistake_llm_files(
    mistake: Mistake,
    request: HttpRequest,
    *,
    client: services.DifyClient,
    user_id: str,
) -> list[Dict[str, str]]:
    if getattr(settings, "DIFY_FILES_UPLOAD_ENABLED", False):
        return _build_mistake_llm_files_upload(
            mistake,
            client=client,
            user_id=user_id,
        )
    return _build_mistake_llm_files_remote(mistake, request)


def _dify_timestamp_to_datetime(timestamp: Any):
    try:
        ts = float(timestamp)
    except (TypeError, ValueError):
        return None
    try:
        dt = datetime.fromtimestamp(ts, tz=dt_timezone.utc)
    except (OverflowError, OSError, ValueError):
        return None
    return timezone.localtime(dt)


def _dify_payload_to_messages(payload: Dict[str, Any]) -> list[Dict[str, Any]]:
    messages: list[Dict[str, Any]] = []
    records = payload.get("data") or []
    sequence = 0
    for record in records:
        created_at = _dify_timestamp_to_datetime(record.get("created_at"))
        query = record.get("query")
        if query:
            messages.append(
                {
                    "sender": "student",
                    "content": query,
                    "created_at": created_at,
                    "_sequence": sequence,
                }
            )
            sequence += 1
        answer = record.get("answer")
        if answer:
            messages.append(
                {
                    "sender": "llm",
                    "content": answer,
                    "created_at": created_at,
                    "_sequence": sequence,
                }
            )
            sequence += 1

    if messages:
        fallback_created_at = timezone.now()

        def _message_sort_key(item: Dict[str, Any]):
            created_at = item.get("created_at") or fallback_created_at
            return (created_at, item.get("_sequence", 0))

        messages.sort(key=_message_sort_key)
        for message in messages:
            message.pop("_sequence", None)
    return messages


class StudentRequiredMixin(UserPassesTestMixin):
    raise_exception = True

    def test_func(self):
        user = self.request.user
        return user.is_authenticated and getattr(user, "is_student", False)


class TeacherRequiredMixin(UserPassesTestMixin):
    raise_exception = True

    def test_func(self):
        user = self.request.user
        return user.is_authenticated and getattr(user, "is_teacher", False)


class HomePageView(TemplateView):
    template_name = "learning/home.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["feature_highlights"] = [
            ("错题收藏与反思", "沉淀每一道错题的解题过程、正确思路和反思笔记，形成个人知识库。"),
            ("老师批阅反馈", "实时查看老师批阅进度与点评建议，及时查漏补缺。"),
            ("智能助教问答", "接入大型语言模型，为每一道错题提供个性化解析与拓展练习建议。"),
            ("学习笔记分享", "撰写富文本笔记并提交老师审核，优秀内容将进入公共笔记区。"),
        ]
        context["cta_links"] = [
            ("立即登录", reverse_lazy("accounts:login")),
            ("创建学生账号", reverse_lazy("accounts:register")),
        ]
        return context


class MistakeListView(LoginRequiredMixin, ListView):
    model = Mistake
    context_object_name = "mistakes"
    template_name = "learning/mistake_list.html"

    def get_queryset(self):
        user = self.request.user
        if getattr(user, "is_teacher", False):
            base_queryset = (
                Mistake.objects.filter(student__assigned_teacher=user)
                .select_related("student", "student__assigned_teacher")
            )
        else:
            base_queryset = (
                Mistake.objects.filter(student=user)
                .select_related("student", "student__assigned_teacher")
            )
        self.base_queryset = base_queryset
        return base_queryset.filter(is_archived=False)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        base_queryset = getattr(self, "base_queryset", Mistake.objects.none())
        mistakes = context["mistakes"]
        context["draft_count"] = mistakes.filter(status=Mistake.Status.DRAFT).count()
        context["submitted_count"] = mistakes.filter(
            status=Mistake.Status.SUBMITTED
        ).count()
        context["resolved_count"] = mistakes.filter(
            status=Mistake.Status.RESOLVED
        ).count()
        context["recent_activity"] = mistakes.order_by("-updated_at")[:6]
        total = (
            context["draft_count"]
            + context["submitted_count"]
            + context["resolved_count"]
        )
        context["total_count"] = total
        context["submitted_percent"] = (
            int(context["submitted_count"] / total * 100) if total else 0
        )
        context["resolved_percent"] = (
            int(context["resolved_count"] / total * 100) if total else 0
        )
        archived_qs = base_queryset.filter(is_archived=True).select_related(
            "student", "student__assigned_teacher"
        )
        context["archived_count"] = archived_qs.count()
        context["archived_mistakes"] = archived_qs.order_by("-archived_at", "-updated_at")

        user = self.request.user
        if getattr(user, "is_teacher", False):
            context["page_subtitle"] = "管理学生错题，追踪批阅进度，提高班级复习效率。"
            context["scope_label"] = "管理学生数"
            context["scope_value"] = mistakes.values("student_id").distinct().count()
            context["scope_percent"] = 100 if context["scope_value"] else 0
        else:
            context["page_subtitle"] = "梳理错题，查漏补缺，构建个人知识图谱。"
            context["scope_label"] = "近 7 天坚持次数"
            context["scope_value"] = mistakes.filter(
                updated_at__gte=timezone.now() - timedelta(days=7)
            ).count()
            context["scope_percent"] = (
                min(context["scope_value"] * 20, 100) if context["scope_value"] else 0
            )
        return context


class MistakeDetailView(LoginRequiredMixin, DetailView):
    model = Mistake
    context_object_name = "mistake"
    template_name = "learning/mistake_detail.html"

    def get_queryset(self):
        user = self.request.user
        visibility_rules = models.Q(student=user) | models.Q(
            student__assigned_teacher=user
        )
        # 允许查看已在公共笔记中引用的错题
        public_notebook_rule = models.Q(
            notebook_entries__status=NotebookEntry.Status.PUBLIC
        )
        return (
            Mistake.objects.filter(visibility_rules | public_notebook_rule)
            .select_related("student", "student__assigned_teacher")
            .distinct()
        )

    def _has_public_notebook_access(self, mistake: Mistake) -> bool:
        """Check whether the mistake is exposed via a public notebook."""
        user = self.request.user
        if not user.is_authenticated:
            return False

        if mistake.student_id == user.id:
            return True

        assigned_teacher_id = getattr(mistake.student, "assigned_teacher_id", None)
        if getattr(user, "is_teacher", False) and assigned_teacher_id == user.id:
            return True

        link_fragment = f"mistakes/{mistake.pk}/"
        return NotebookEntry.objects.filter(
            status=NotebookEntry.Status.PUBLIC
        ).filter(
            models.Q(related_mistakes=mistake)
            | models.Q(content__icontains=link_fragment)
        ).exists()

    def get_object(self, queryset=None):
        try:
            return super().get_object(queryset)
        except Http404:
            pk = self.kwargs.get(self.pk_url_kwarg)
            if pk is None:
                raise

            fallback_obj = (
                Mistake.objects.select_related("student", "student__assigned_teacher")
                .filter(pk=pk)
                .first()
            )
            if fallback_obj and self._has_public_notebook_access(fallback_obj):
                return fallback_obj
            raise

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        mistake: Mistake = context["mistake"]
        context["llm_messages"] = []
        context["llm_error"] = ""
        user = self.request.user
        can_manage_llm = getattr(user, "is_student", False) and mistake.student_id == user.id
        assigned_teacher_id = getattr(mistake.student, "assigned_teacher_id", None)
        can_review_llm = getattr(user, "is_teacher", False) and assigned_teacher_id == user.id
        context["can_manage_llm"] = can_manage_llm
        context["can_review_llm"] = can_review_llm
        context["can_view_llm"] = can_manage_llm or can_review_llm

        api_key = getattr(settings, "DIFY_API_KEY", "")
        if api_key and mistake.llm_conversation_id:
            client = services.DifyClient(api_key=api_key)
            try:
                payload = client.get_messages(
                    user_id=f"user-{mistake.student_id}",
                    conversation_id=mistake.llm_conversation_id,
                )
            except services.DifyClientError as exc:
                context["llm_error"] = str(exc)
            else:
                context["llm_messages"] = _dify_payload_to_messages(payload)

        return context


class MistakeCreateView(StudentRequiredMixin, LoginRequiredMixin, CreateView):
    form_class = MistakeForm
    model = Mistake
    template_name = "learning/mistake_form.html"

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.student = self.request.user
        self.object.save()
        return super().form_valid(form)


class MistakeResolveView(TeacherRequiredMixin, LoginRequiredMixin, View):
    success_url = reverse_lazy("learning:mistake_list")

    def post(self, request, pk):
        mistake = get_object_or_404(
            Mistake,
            pk=pk,
            student__assigned_teacher=request.user,
        )
        feedback = request.POST.get("feedback", "").strip()
        mistake.resolve(reviewer=request.user, feedback=feedback)
        UserNotification.objects.create(
            user=mistake.student,
            title="错题已批阅",
            message=f"老师给出反馈：{feedback or '请查看详情'}",
            link=mistake.get_absolute_url(),
            category=UserNotification.Category.MESSAGE,
        )
        return redirect(mistake.get_absolute_url())


class MistakeArchiveView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        if mistake.status != Mistake.Status.RESOLVED:
            messages.error(request, "仅已批阅的错题支持归档，请先完成老师反馈。")
            next_url = request.POST.get("next") or mistake.get_absolute_url()
            return redirect(next_url)
        mistake.archive()
        messages.success(request, "错题已归档，可在归档列表中查看。")
        next_url = request.POST.get("next") or reverse_lazy("learning:mistake_list")
        return redirect(next_url)


class MistakeRestoreView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        mistake.restore()
        messages.success(request, "已恢复该错题，重新显示在列表中。")
        next_url = request.POST.get("next") or mistake.get_absolute_url()
        return redirect(next_url)


class MistakeDeleteView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        if mistake.status != Mistake.Status.DRAFT:
            messages.error(request, "仅草稿状态的错题可以删除。")
            return redirect(mistake.get_absolute_url())

        next_url = request.POST.get("next") or reverse_lazy("learning:mistake_list")
        mistake.delete()
        messages.success(request, "草稿错题已删除。")
        return redirect(next_url)


class NotebookAttachmentMixin:
    def _save_attachments(self, entry: NotebookEntry):
        files = self.request.FILES.getlist("attachments")
        for uploaded in files:
            if uploaded:
                NotebookAttachment.objects.create(entry=entry, image=uploaded)


class NotebookListView(StudentRequiredMixin, LoginRequiredMixin, ListView):
    model = NotebookEntry
    context_object_name = "entries"
    template_name = "learning/notebook_list.html"

    def get_queryset(self):
        return (
            NotebookEntry.objects.filter(student=self.request.user)
            .prefetch_related("attachments", "related_mistakes")
            .select_related("student")
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        entries = context["entries"]
        context["draft_entries"] = entries.filter(status=NotebookEntry.Status.DRAFT)
        context["submitted_entries"] = entries.filter(
            status=NotebookEntry.Status.SUBMITTED
        )
        context["public_entries"] = entries.filter(status=NotebookEntry.Status.PUBLIC)
        context["total_count"] = entries.count()
        context["pending_count"] = context["submitted_entries"].count()
        return context


class NotebookCreateView(
    NotebookAttachmentMixin, StudentRequiredMixin, LoginRequiredMixin, CreateView
):
    model = NotebookEntry
    form_class = NotebookEntryForm
    template_name = "learning/notebook_form.html"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["student"] = self.request.user
        return kwargs

    def form_valid(self, form):
        entry = form.save(commit=False)
        entry.student = self.request.user
        entry.status = NotebookEntry.Status.DRAFT
        entry.submitted_at = None
        entry.published_at = None
        entry.save()
        form.save_m2m()
        self._save_attachments(entry)
        messages.success(self.request, "笔记已保存为草稿。")
        return redirect(entry.get_absolute_url())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["mistake_options"] = (
            self.request.user.mistakes.select_related("student").order_by("-updated_at")
        )
        context["is_edit"] = False
        return context


class NotebookUpdateView(
    NotebookAttachmentMixin, StudentRequiredMixin, LoginRequiredMixin, UpdateView
):
    model = NotebookEntry
    form_class = NotebookEntryForm
    template_name = "learning/notebook_form.html"
    context_object_name = "entry"

    def get_queryset(self):
        return (
            NotebookEntry.objects.filter(student=self.request.user)
            .prefetch_related("attachments", "related_mistakes")
            .select_related("student")
        )

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["student"] = self.request.user
        return kwargs

    def form_valid(self, form):
        entry = form.save(commit=False)
        original_status = entry.status
        if original_status != NotebookEntry.Status.DRAFT:
            entry.status = NotebookEntry.Status.DRAFT
            entry.submitted_at = None
            if original_status == NotebookEntry.Status.PUBLIC:
                entry.published_at = None
        entry.student = self.request.user
        entry.save()
        form.save_m2m()
        self._save_attachments(entry)
        remove_ids = self.request.POST.getlist("remove_attachments")
        if remove_ids:
            entry.attachments.filter(
                pk__in=remove_ids, entry__student=self.request.user
            ).delete()
        messages.success(self.request, "笔记内容已更新。")
        return redirect(entry.get_absolute_url())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["mistake_options"] = (
            self.request.user.mistakes.select_related("student").order_by("-updated_at")
        )
        context["is_edit"] = True
        context["attachments"] = self.object.attachments.all()
        return context


class NotebookDetailView(DetailView):
    model = NotebookEntry
    context_object_name = "entry"
    template_name = "learning/notebook_detail.html"

    def get_queryset(self):
        base_queryset = (
            NotebookEntry.objects.select_related("student", "student__assigned_teacher")
            .prefetch_related("attachments", "related_mistakes")
            .all()
        )
        if not self.request.user.is_authenticated:
            return base_queryset.filter(status=NotebookEntry.Status.PUBLIC)

        user = self.request.user
        allowed = models.Q(status=NotebookEntry.Status.PUBLIC)
        if getattr(user, "is_student", False):
            allowed |= models.Q(student=user)
        if getattr(user, "is_teacher", False):
            allowed |= models.Q(student__assigned_teacher=user)
        return base_queryset.filter(allowed)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        entry: NotebookEntry = context["entry"]
        user = self.request.user
        context["can_edit"] = (
            user.is_authenticated and getattr(user, "is_student", False) and entry.student_id == user.id
        )
        context["can_submit"] = (
            context["can_edit"] and entry.status == NotebookEntry.Status.DRAFT
        )
        context["can_withdraw"] = (
            context["can_edit"] and entry.status == NotebookEntry.Status.SUBMITTED
        )
        context["can_publish"] = (
            user.is_authenticated
            and getattr(user, "is_teacher", False)
            and entry.status == NotebookEntry.Status.SUBMITTED
            and getattr(entry.student, "assigned_teacher_id", None) == user.id
        )
        context["is_public"] = entry.status == NotebookEntry.Status.PUBLIC
        context["related_mistakes"] = entry.related_mistakes.all()
        context["attachments"] = entry.attachments.all()
        return context


class NotebookSubmitView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        entry = get_object_or_404(NotebookEntry, pk=pk, student=request.user)
        if entry.status == NotebookEntry.Status.PUBLIC:
            messages.error(request, "公开笔记无需重复提交。")
            return redirect(entry.get_absolute_url())
        entry.submit_for_review()
        teacher = getattr(request.user, "assigned_teacher", None)
        if teacher:
            UserNotification.objects.create(
                user=teacher,
                title="学生提交了新的笔记",
                message=f"{request.user.username} 提交了《{entry.title}》等待审核。",
                link=entry.get_absolute_url(),
                category=UserNotification.Category.MESSAGE,
            )
        messages.success(request, "笔记已提交审核，等待老师批准后即可公开。")
        return redirect(entry.get_absolute_url())


class NotebookWithdrawView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        entry = get_object_or_404(NotebookEntry, pk=pk, student=request.user)
        if entry.status != NotebookEntry.Status.SUBMITTED:
            messages.error(request, "当前状态无法撤回。")
            return redirect(entry.get_absolute_url())
        entry.revert_to_draft()
        messages.info(request, "已撤回提交，笔记回到草稿状态。")
        return redirect(entry.get_absolute_url())


class NotebookPublishView(TeacherRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        entry = get_object_or_404(
            NotebookEntry,
            pk=pk,
            student__assigned_teacher=request.user,
        )
        if entry.status != NotebookEntry.Status.SUBMITTED:
            messages.error(request, "仅待审核的笔记可以发布。")
            return redirect(entry.get_absolute_url())
        entry.publish(reviewer=request.user)
        UserNotification.objects.create(
            user=entry.student,
            title="笔记已通过审核",
            message=f"《{entry.title}》已发布到公共笔记。",
            link=entry.get_absolute_url(),
            category=UserNotification.Category.MESSAGE,
        )
        messages.success(request, "已发布该笔记至公共笔记。")
        return redirect(entry.get_absolute_url())


class NotebookRejectView(TeacherRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        entry = get_object_or_404(
            NotebookEntry,
            pk=pk,
            student__assigned_teacher=request.user,
        )
        if entry.status != NotebookEntry.Status.SUBMITTED:
            messages.error(request, "仅待审核的笔记可以驳回。")
            return redirect(entry.get_absolute_url())
        entry.revert_to_draft(reviewer=request.user)
        UserNotification.objects.create(
            user=entry.student,
            title="笔记审核未通过",
            message=f"《{entry.title}》被退回，请修改后重新提交。",
            link=entry.get_absolute_url(),
            category=UserNotification.Category.MESSAGE,
        )
        messages.info(request, "已驳回该笔记，学生会收到通知。")
        return redirect(entry.get_absolute_url())


class NotebookPublicListView(ListView):
    model = NotebookEntry
    context_object_name = "public_entries"
    template_name = "learning/notebook_public_list.html"
    paginate_by = 12

    def get_queryset(self):
        return (
            NotebookEntry.objects.filter(status=NotebookEntry.Status.PUBLIC)
            .select_related("student")
            .prefetch_related("attachments", "related_mistakes")
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = "公共笔记"
        return context


class MistakeAskLLMView(StudentRequiredMixin, LoginRequiredMixin, View):
    form_class = LLMQuestionForm

    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        
        # 日志记录
        import logging
        logger = logging.getLogger(__name__)
        logger.debug(f"Request content type: {request.content_type}")
        
        # 尝试从不同来源获取问题数据
        try:
            if request.content_type and 'application/json' in request.content_type:
                import json
                json_data = json.loads(request.body.decode('utf-8'))
                question_text = json_data.get('question', '').strip()
                logger.debug(f"Got question from JSON: {question_text}")
            else:
                # 对于 multipart/form-data 或 application/x-www-form-urlencoded
                logger.debug(f"POST data keys: {list(request.POST.keys())}")
                question_text = request.POST.get('question', '').strip()
                logger.debug(f"Got question from POST: {question_text[:100] if question_text else 'empty'}")
            
            # 如果还是没有问题文本，尝试从表单验证
            if not question_text:
                form = self.form_class(request.POST)
                if form.is_valid():
                    question_text = form.cleaned_data["question"]
                else:
                    logger.error(f"Form validation errors: {form.errors}")
                    return JsonResponse({"errors": form.errors}, status=400)
            else:
                # 手动验证问题文本
                if len(question_text) > 1000:
                    return JsonResponse(
                        {"errors": {"question": ["问题内容不能超过1000个字符"]}}, 
                        status=400
                    )
                if not question_text:
                    return JsonResponse(
                        {"errors": {"question": ["请输入您的问题"]}}, 
                        status=400
                    )
                question = question_text
        except json.JSONDecodeError as e:
            logger.error(f"JSON decode error: {e}")
            return JsonResponse(
                {"errors": {"__all__": ["无法解析请求数据"]}}, 
                status=400
            )
        except Exception as e:
            logger.error(f"Unexpected error parsing request: {e}", exc_info=True)
            return JsonResponse(
                {"errors": {"__all__": [f"处理请求时出错: {str(e)}"]}}, 
                status=400
            )

        api_key = getattr(settings, "DIFY_API_KEY", "")
        if not api_key:
            logger.error("DIFY_API_KEY not configured")
            return JsonResponse(
                {"errors": {"__all__": ["AI服务未配置，请联系管理员"]}},
                status=500
            )

        client = services.DifyClient(api_key=api_key)
        previous_conversation_id = mistake.llm_conversation_id
        user_identifier = f"user-{mistake.student_id}"
        
        try:
            files_payload = _build_mistake_llm_files(
                mistake,
                request,
                client=client,
                user_id=user_identifier,
            )
        except Exception as e:
            logger.error(f"Error building files payload: {e}", exc_info=True)
            files_payload = []

        try:
            logger.debug(f"Sending question to Dify: {question[:100]}...")
            response = client.ask(
                query=question,
                user_id=user_identifier,
                conversation_id=mistake.llm_conversation_id,
                inputs=_build_mistake_llm_inputs(mistake),
                files=files_payload,
            )
            logger.debug(f"Received response from Dify: {str(response)[:200]}...")
        except services.DifyClientError as exc:
            logger.error(f"Dify client error: {exc}", exc_info=True)
            return JsonResponse({"errors": {"__all__": [str(exc)]}}, status=502)
        except Exception as exc:
            logger.error(f"Unexpected error calling Dify: {exc}", exc_info=True)
            return JsonResponse(
                {"errors": {"__all__": [f"AI服务调用失败: {str(exc)}"]}}, 
                status=502
            )

        answer = response.get("answer") or response.get("output") or ""
        conversation_id = response.get("conversation_id") or mistake.llm_conversation_id
        conversation_name = (
            response.get("conversation_name")
            or response.get("conversationName")
            or response.get("name")
            or ""
        )

        if conversation_id and conversation_id != mistake.llm_conversation_id:
            if previous_conversation_id:
                mistake.llm_conversations.filter(
                    conversation_id=previous_conversation_id
                ).update(is_active=False, archived_at=timezone.now())

            mistake.llm_conversation_id = conversation_id
            mistake.save(update_fields=("llm_conversation_id", "updated_at"))

        if conversation_id:
            convo_defaults = {
                "mistake": mistake,
                "name": conversation_name[:255],
                "is_active": True,
            }
            conversation_obj, created = MistakeLLMConversation.objects.get_or_create(
                conversation_id=conversation_id,
                defaults=convo_defaults,
            )
            updates = {"is_active": True, "archived_at": None}
            if conversation_obj.mistake_id != mistake.id:
                updates["mistake"] = mistake
            if conversation_name:
                updates["name"] = conversation_name[:255]
            if updates:
                for field, value in updates.items():
                    setattr(conversation_obj, field, value)
                conversation_obj.save(update_fields=tuple(updates.keys()))

        logger.info(f"Successfully processed LLM question for mistake {mistake.id}")
        return JsonResponse(
            {
                "answer": answer,
                "conversation_id": conversation_id,
            }
        )


class MistakeGetMessagesView(LoginRequiredMixin, View):
    """获取会话历史消息的 API 视图"""
    
    def get(self, request, pk):
        user = request.user
        if getattr(user, "is_student", False):
            mistake = get_object_or_404(Mistake, pk=pk, student=user)
        elif getattr(user, "is_teacher", False):
            mistake = get_object_or_404(
                Mistake,
                pk=pk,
                student__assigned_teacher=user,
            )
        else:
            return JsonResponse({"error": "permission denied"}, status=403)

        target_user = mistake.student
        conversation_id = mistake.llm_conversation_id
        
        if not conversation_id:
            return JsonResponse({
                "messages": [],
                "has_more": False,
                "conversation_id": None
            })
        
        api_key = getattr(settings, "DIFY_API_KEY", "")
        if not api_key:
            return JsonResponse(
                {"error": "DIFY_API_KEY not configured"},
                status=500
            )
        
        # 获取分页参数
        limit = int(request.GET.get("limit", 20))
        last_id = request.GET.get("last_id", "")
        
        client = services.DifyClient(api_key=api_key)
        try:
            payload = client.get_messages(
                user_id=f"user-{target_user.id}",
                conversation_id=conversation_id,
                limit=limit,
                last_id=last_id,
            )
        except services.DifyClientError as exc:
            return JsonResponse({"error": str(exc)}, status=502)
        
        # 转换消息格式
        messages_data = _dify_payload_to_messages(payload)
        
        return JsonResponse({
            "messages": messages_data,
            "has_more": payload.get("has_more", False),
            "conversation_id": conversation_id,
            "limit": payload.get("limit", limit)
        })


class MistakeCreateConversationView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        previous_id = mistake.llm_conversation_id

        if previous_id:
            mistake.llm_conversations.filter(
                conversation_id=previous_id
            ).update(is_active=False, archived_at=timezone.now())

        mistake.llm_conversation_id = ""
        mistake.save(update_fields=("llm_conversation_id", "updated_at"))
        mistake.llm_messages.all().delete()

        return JsonResponse(
            {
                "conversation_id": "",
                "previous_id": previous_id,
                "message": "已准备新的助教会话。",
            }
        )


class MistakeSwitchConversationView(LoginRequiredMixin, View):
    """Activate a different LLM conversation for the current mistake."""

    def post(self, request, pk):
        user = request.user
        if getattr(user, "is_student", False):
            mistake = get_object_or_404(Mistake, pk=pk, student=user)
        elif getattr(user, "is_teacher", False):
            mistake = get_object_or_404(
                Mistake,
                pk=pk,
                student__assigned_teacher=user,
            )
        else:
            return JsonResponse({"error": "permission denied"}, status=403)

        payload: Dict[str, Any]
        if request.content_type == "application/json":
            try:
                payload = json.loads(request.body.decode("utf-8"))
            except json.JSONDecodeError:
                payload = {}
        else:
            payload = request.POST

        conversation_id = (payload.get("conversation_id") or "").strip()
        conversation_name = (payload.get("name") or "").strip()

        if not conversation_id:
            return JsonResponse({"error": "conversation_id required"}, status=400)

        previous_id = mistake.llm_conversation_id

        if previous_id == conversation_id:
            mistake.llm_conversations.filter(conversation_id=conversation_id).update(
                is_active=True,
                archived_at=None,
            )
            return JsonResponse(
                {
                    "conversation_id": conversation_id,
                    "previous_id": previous_id,
                    "message": "conversation already active",
                }
            )

        if previous_id:
            mistake.llm_conversations.filter(
                conversation_id=previous_id
            ).update(is_active=False, archived_at=timezone.now())

        defaults = {
            "mistake": mistake,
            "is_active": True,
        }
        if conversation_name:
            defaults["name"] = conversation_name[:255]

        conversation_obj, _ = MistakeLLMConversation.objects.get_or_create(
            conversation_id=conversation_id,
            defaults=defaults,
        )

        updates = {"is_active": True, "archived_at": None}
        if conversation_obj.mistake_id != mistake.id:
            updates["mistake"] = mistake
        if conversation_name:
            updates["name"] = conversation_name[:255]

        if updates:
            for field, value in updates.items():
                setattr(conversation_obj, field, value)
            conversation_obj.save(update_fields=tuple(updates.keys()))

        mistake.llm_conversation_id = conversation_id
        mistake.save(update_fields=("llm_conversation_id", "updated_at"))

        return JsonResponse(
            {
                "conversation_id": conversation_id,
                "previous_id": previous_id,
                "message": "conversation switched",
            }
        )


class MistakeResetLLMView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        conversation_id = mistake.llm_conversation_id
        api_key = getattr(settings, "DIFY_API_KEY", "")
        if not conversation_id:
            mistake.llm_messages.all().delete()
            messages.info(request, "当前没有可清除的智能助教会话。")
            return redirect(mistake.get_absolute_url())
        if not api_key:
            raise ImproperlyConfigured("DIFY_API_KEY must be configured to manage LLM conversations.")

        client = services.DifyClient(api_key=api_key)
        try:
            client.delete_conversation(
                user_id=f"user-{mistake.student_id}",
                conversation_id=conversation_id,
            )
        except services.DifyClientError as exc:
            messages.error(request, f"重置会话失败：{exc}")
            return redirect(mistake.get_absolute_url())

        mistake.llm_conversations.filter(
            conversation_id=conversation_id
        ).update(is_active=False, archived_at=timezone.now())

        mistake.llm_conversation_id = ""
        mistake.save(update_fields=("llm_conversation_id", "updated_at"))
        mistake.llm_messages.all().delete()
        messages.success(request, "已清空智能助教会话，欢迎重新提问。")
        return redirect(mistake.get_absolute_url())


class MistakeListConversationsView(LoginRequiredMixin, View):
    """获取用户所有会话列表的 API 视图"""
    
    def get(self, request):
        api_key = getattr(settings, "DIFY_API_KEY", "")
        if not api_key:
            return JsonResponse(
                {"error": "DIFY_API_KEY not configured"},
                status=500
            )
        
        # 获取分页参数
        try:
            limit = int(request.GET.get("limit", 20))
        except (ValueError, TypeError):
            limit = 20
        last_id = request.GET.get("last_id", "")
        user = request.user

        if getattr(user, "is_student", False):
            target_user = user
        elif getattr(user, "is_teacher", False):
            student_id = request.GET.get("student")
            if not student_id:
                return JsonResponse({"error": "student parameter required"}, status=400)
            UserModel = get_user_model()
            try:
                target_user = UserModel.objects.get(
                    pk=student_id,
                    assigned_teacher=user,
                )
            except (UserModel.DoesNotExist, ValueError):
                return JsonResponse({"error": "student not found"}, status=404)
        else:
            return JsonResponse({"error": "permission denied"}, status=403)
        
        client = services.DifyClient(api_key=api_key)
        try:
            payload = client.list_conversations(
                user_id=f"user-{target_user.id}",
                limit=limit,
                last_id=last_id,
            )
        except services.DifyClientError as exc:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Dify list_conversations error: {exc}")
            return JsonResponse({"error": str(exc)}, status=502)
        except Exception as exc:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Unexpected error in list_conversations: {exc}", exc_info=True)
            return JsonResponse({"error": "Internal server error"}, status=500)
        
        # 获取本地错题记录，匹配会话ID
        try:
            conv_ids = [c["id"] for c in payload.get("data", []) if "id" in c]
            local_conversations: dict[str, MistakeLLMConversation] = {}

            if conv_ids:
                conversations_qs = (
                    MistakeLLMConversation.objects.filter(
                        conversation_id__in=conv_ids,
                        mistake__student=target_user,
                    )
                    .select_related("mistake", "mistake__student")
                )
                local_conversations = {
                    conv.conversation_id: conv for conv in conversations_qs
                }

                missing_ids = set(conv_ids) - set(local_conversations.keys())
                if missing_ids:
                    fallback_mistakes = (
                        Mistake.objects.filter(
                            student=target_user,
                            llm_conversation_id__in=missing_ids,
                        )
                        .select_related("student")
                    )
                    for mistake in fallback_mistakes:
                        conv_obj, _ = MistakeLLMConversation.objects.get_or_create(
                            conversation_id=mistake.llm_conversation_id,
                            defaults={
                                "mistake": mistake,
                                "is_active": True,
                            },
                        )
                        conv_obj.mistake = mistake
                        conv_obj.is_active = True
                        conv_obj.archived_at = None
                        conv_obj.save(update_fields=("mistake", "is_active", "archived_at"))
                        local_conversations[conv_obj.conversation_id] = conv_obj
        except Exception as exc:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Error fetching local conversations: {exc}", exc_info=True)
            local_conversations = {}
        
        # 增强会话信息
        conversations = []
        for conv in payload.get("data", []):
            conv_id = conv.get("id")
            if not conv_id:
                continue
                
            conv_data = {
                "id": conv_id,
                "name": conv.get("name") or "",
                "status": conv.get("status", "normal"),
                "created_at": conv.get("created_at"),
                "updated_at": conv.get("updated_at"),
            }
            
            # 添加关联的错题信息
            local_conv = local_conversations.get(conv_id)
            if local_conv and not conv_data["name"]:
                conv_data["name"] = local_conv.name

            if local_conv:
                mistake = local_conv.mistake
                conv_data["mistake"] = {
                    "id": mistake.id,
                    "title": mistake.title,
                    "url": mistake.get_absolute_url(),
                    "status": mistake.get_status_display(),
                }
                conv_data["is_active_local"] = local_conv.is_active
                conv_data["student"] = {
                    "id": mistake.student_id,
                    "username": mistake.student.username,
                }
                if not conv_data["name"]:
                    conv_data["name"] = f"{mistake.title} 的对话"
            else:
                conv_data["mistake"] = None
                conv_data["is_active_local"] = False
                conv_data["student"] = {
                    "id": target_user.id,
                    "username": target_user.username,
                }
            
            conversations.append(conv_data)
        
        return JsonResponse({
            "conversations": conversations,
            "has_more": payload.get("has_more", False),
            "limit": payload.get("limit", limit),
        })


class MistakeSubmitView(StudentRequiredMixin, LoginRequiredMixin, View):
    def post(self, request, pk):
        mistake = get_object_or_404(Mistake, pk=pk, student=request.user)
        if mistake.status != Mistake.Status.DRAFT:
            return redirect(mistake.get_absolute_url())
        mistake.submit_for_review()
        return redirect(mistake.get_absolute_url())


# ============================================================
# 专题训练相关视图
# ============================================================

class TopicTrainingListView(StudentRequiredMixin, LoginRequiredMixin, ListView):
    """专题训练列表页"""
    model = TopicTraining
    context_object_name = "trainings"
    template_name = "learning/topic_training_list.html"

    def get_queryset(self):
        return TopicTraining.objects.filter(is_active=True)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        user = self.request.user
        
        # 获取学生的训练进度
        user_progress = TopicProgress.objects.filter(
            student=user
        ).select_related("training", "current_question")
        
        # 构建进度字典
        progress_dict = {p.training_id: p for p in user_progress}
        context["progress_dict"] = progress_dict
        
        return context


class TopicTrainingStartView(StudentRequiredMixin, LoginRequiredMixin, View):
    """开始专题训练"""
    
    def get(self, request, pk):
        training = get_object_or_404(TopicTraining, pk=pk, is_active=True)
        
        # 获取或创建进度记录
        progress, created = TopicProgress.objects.get_or_create(
            student=request.user,
            training=training,
            defaults={
                "current_question": training.questions.filter(
                    question_number=1
                ).first()
            }
        )
        
        # 如果已完成，重新开始
        if progress.is_completed:
            progress.is_completed = False
            progress.completed_at = None
            progress.answer_history = []
            progress.current_question = training.questions.filter(
                question_number=1
            ).first()
            progress.llm_conversation_id = ""
            progress.save()
        
        return redirect("learning:topic_training_question", pk=training.pk)


class TopicTrainingQuestionView(StudentRequiredMixin, LoginRequiredMixin, DetailView):
    """显示当前题目"""
    model = TopicTraining
    context_object_name = "training"
    template_name = "learning/topic_training_question.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        training = self.object
        
        # 获取学生的训练进度
        progress = get_object_or_404(
            TopicProgress,
            student=self.request.user,
            training=training
        )
        
        context["progress"] = progress
        context["current_question"] = progress.current_question
        context["answer_count"] = len(progress.answer_history)
        
        # 检查是否已完成
        if progress.is_completed or not progress.current_question:
            context["is_completed"] = True
        else:
            context["is_completed"] = False
        
        return context


def _build_topic_training_files(
    answer_image_file,
    question_image_file,
    request: HttpRequest,
    *,
    client: services.DifyClient,
    user_id: str,
) -> list[Dict[str, str]]:
    """构建专题训练的图片文件列表，传递给Dify"""
    files_payload = []
    seen_urls = set()
    
    # 处理题目图片
    if question_image_file:
        try:
            file_url = question_image_file.url
            absolute_url = _resolve_dify_file_url(request, file_url)
            if absolute_url not in seen_urls:
                seen_urls.add(absolute_url)
                files_payload.append({
                    "type": "image",
                    "transfer_method": "remote_url",
                    "url": absolute_url,
                })
        except (AttributeError, ValueError):
            pass
    
    # 处理学生上传的答案图片
    if answer_image_file:
        if getattr(settings, "DIFY_FILES_UPLOAD_ENABLED", False):
            # 使用上传方式
            try:
                answer_image_file.open("rb")
            except FileNotFoundError:
                pass
            else:
                try:
                    data = answer_image_file.read()
                finally:
                    answer_image_file.close()
                
                if data:
                    file_name = (answer_image_file.name or "answer_image.png").split("/")[-1]
                    content_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
                    
                    try:
                        upload_response = client.upload_file(
                            user_id=user_id,
                            file_name=file_name,
                            file_data=data,
                            content_type=content_type,
                        )
                        upload_id = upload_response.get("id") or upload_response.get("file_id")
                        if upload_id:
                            files_payload.append({
                                "type": "image",
                                "transfer_method": "local_file",
                                "upload_file_id": upload_id,
                            })
                    except services.DifyClientError:
                        pass
        else:
            # 使用远程URL方式
            try:
                file_url = answer_image_file.url
                absolute_url = _resolve_dify_file_url(request, file_url)
                if absolute_url not in seen_urls:
                    seen_urls.add(absolute_url)
                    files_payload.append({
                        "type": "image",
                        "transfer_method": "remote_url",
                        "url": absolute_url,
                    })
            except (AttributeError, ValueError):
                pass
    
    return files_payload


class TopicTrainingSubmitAnswerView(StudentRequiredMixin, LoginRequiredMixin, View):
    """提交答案并获取AI解析"""
    
    def post(self, request, pk):
        import logging
        logger = logging.getLogger(__name__)
        logger.debug(f"TopicTrainingSubmitAnswerView called for training {pk}")
        logger.debug(f"Content-Type: {request.content_type}")
        logger.debug(f"POST data: {request.POST}")
        logger.debug(f"FILES data: {request.FILES}")
        
        training = get_object_or_404(TopicTraining, pk=pk, is_active=True)
        progress = get_object_or_404(
            TopicProgress,
            student=request.user,
            training=training
        )
        
        if not progress.current_question:
            return JsonResponse(
                {"error": "没有当前题目"},
                status=400
            )
        
        # 获取学生的答案和图片
        try:
            if request.content_type and 'application/json' in request.content_type:
                # JSON格式不支持文件上传
                data = json.loads(request.body.decode('utf-8'))
                student_answer = data.get('answer', '').strip()
                answer_image = None
            else:
                # FormData格式，支持文件上传
                student_answer = request.POST.get('answer', '').strip()
                answer_image = request.FILES.get('answer_image')
        except json.JSONDecodeError:
            return JsonResponse(
                {"error": "无法解析请求数据"},
                status=400
            )
        
        logger.debug(f"Student answer: {student_answer[:100] if student_answer else 'empty'}")
        logger.debug(f"Answer image: {answer_image}")
        
        if not student_answer:
            return JsonResponse(
                {"error": "请输入您的答案"},
                status=400
            )
        
        current_question = progress.current_question
        
        # 调用 Dify AI 获取解析
        api_key = getattr(settings, "DIFY_API_KEY", "")
        if not api_key:
            return JsonResponse(
                {"error": "AI服务未配置"},
                status=500
            )
        
        client = services.DifyClient(api_key=api_key)
        user_identifier = f"user-{request.user.id}"
        
        # 构建给 AI 的输入
        inputs = {
            "question": current_question.question_text,
            "question_number": str(current_question.question_number),
            "student_answer": student_answer,
            "correct_answer": current_question.correct_answer,
            "explanation": current_question.explanation or "",
        }
        
        # 处理题目图片和学生答案图片
        files_payload = _build_topic_training_files(
            answer_image,
            current_question.question_image,
            request,
            client=client,
            user_id=user_identifier,
        )
        
        # 构建提示词
        has_answer_image = answer_image is not None
        query = f"""请分析学生的答案是否正确，并给出详细解析。

题目：{current_question.question_text}

学生的答案：{student_answer}
{"学生还上传了答案图片，请结合图片内容进行分析。" if has_answer_image else ""}

正确答案：{current_question.correct_answer}

请你：
1. 判断学生答案是否正确
2. 指出学生答案的优点和不足
3. 给出详细的解题思路和知识点讲解
4. 如果答错了，帮助学生找出错误原因"""
        
        try:
            response = client.ask(
                query=query,
                user_id=user_identifier,
                conversation_id=progress.llm_conversation_id,
                inputs=inputs,
                files=files_payload,
            )
        except services.DifyClientError as exc:
            return JsonResponse({"error": str(exc)}, status=502)
        
        answer = response.get("answer") or response.get("output") or ""
        conversation_id = response.get("conversation_id") or progress.llm_conversation_id
        
        # 更新会话ID
        if conversation_id and conversation_id != progress.llm_conversation_id:
            progress.llm_conversation_id = conversation_id
            progress.save(update_fields=["llm_conversation_id", "updated_at"])
        
        return JsonResponse({
            "answer": answer,
            "conversation_id": conversation_id,
            "question_number": current_question.question_number,
            "correct_answer": current_question.correct_answer,
        })


class TopicTrainingNextQuestionView(StudentRequiredMixin, LoginRequiredMixin, View):
    """学生选择答对或答错后跳转到下一题"""
    
    def post(self, request, pk):
        training = get_object_or_404(TopicTraining, pk=pk, is_active=True)
        progress = get_object_or_404(
            TopicProgress,
            student=request.user,
            training=training
        )
        
        if not progress.current_question:
            return JsonResponse(
                {"error": "没有当前题目"},
                status=400
            )
        
        try:
            if request.content_type and 'application/json' in request.content_type:
                data = json.loads(request.body.decode('utf-8'))
                is_correct = data.get('is_correct') == True or data.get('is_correct') == 'true'
                student_answer = data.get('answer', '').strip()
                has_image = data.get('has_image', False) or bool(data.get('answer_image_url', '').strip())
            else:
                is_correct = request.POST.get('is_correct') == 'true'
                student_answer = request.POST.get('answer', '').strip()
                has_image = bool(request.POST.get('answer_image_url', '').strip())
        except json.JSONDecodeError:
            return JsonResponse(
                {"error": "无法解析请求数据"},
                status=400
            )
        
        current_question = progress.current_question
        
        # 记录答题
        progress.record_answer(current_question, student_answer, is_correct, has_image=has_image)
        
        # 获取下一题
        next_question = progress.get_next_question(is_correct)
        
        if next_question:
            # 还有下一题
            progress.current_question = next_question
            progress.save(update_fields=["current_question", "updated_at"])
            
            return JsonResponse({
                "has_next": True,
                "next_question_number": next_question.question_number,
                "message": f"正在跳转到第 {next_question.question_number} 题"
            })
        else:
            # 训练完成
            progress.is_completed = True
            progress.completed_at = timezone.now()
            progress.current_question = None
            progress.save(update_fields=[
                "is_completed",
                "completed_at",
                "current_question",
                "updated_at"
            ])
            
            return JsonResponse({
                "has_next": False,
                "is_completed": True,
                "message": "恭喜你完成了本次专题训练！"
            })
